/*
* commentsmodel.cpp - This file is the part of the wp-notifier.(code name "luan luan")
* Copyright (C) 2010 Dean Lee <deanlee3@gmail.com>, http://www.deanlee.cn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
* Acknowledgements:
*   <> Many thanks to all of you, who have encouraged me to update my articles
*     and code, and who sent in bug reports and fixes.
* 
*/
#include <QFile>
#include <QMessageBox>
#include "commentsmodel.h"
#include "configdlg.h"
#include "common.h"
#include "blog.h"

///////////////////////////////////////////////////////////
// CommentsModel
///////////////////////////////////////////////////////////
CommentsModel::CommentsModel(QObject * parent) : QAbstractListModel(parent)
{
	m_refreshCount = m_nLastId = 0;
}


CommentsModel::~CommentsModel()
{
	qDeleteAll(m_comments);
	m_comments.clear();
}

void CommentsModel::setBlog(const Blog& blog)
{
	m_blog = blog;
}

void CommentsModel::fromXml(const QDomElement& el)
{
	m_blog.fromXml(el);
	readNumEntry(el, "approve", &m_commentOffset[CommentData::approve].m_count);
	readNumEntry(el, "hold", &m_commentOffset[CommentData::hold].m_count);
	readNumEntry(el, "spam", &m_commentOffset[CommentData::spam].m_count);
	readNumEntry(el, "lastcommentid", &m_nLastId);
	QString datetime;
	readEntry(el, "lastcommentdatetime", &datetime);
	m_lastDateTime = QDateTime::fromString(datetime, Qt::ISODate);

}

QDomElement CommentsModel::toXml(QDomDocument *doc)const
{
	QDomElement el = doc->createElement("blog");
	m_blog.toXml(el);
	el.appendChild( textTag(*doc, "approve", QString::number(m_commentOffset[CommentData::approve].m_count)));
	el.appendChild( textTag(*doc, "hold", QString::number(m_commentOffset[CommentData::hold].m_count)));
	el.appendChild( textTag(*doc, "spam", QString::number(m_commentOffset[CommentData::spam].m_count)));
	el.appendChild( textTag(*doc, "lastcommentid", QString::number(m_nLastId)));
	el.appendChild( textTag(*doc, "lastcommentdatetime", m_lastDateTime.toString(Qt::ISODate)));
	return el;
}


void CommentsModel::refresh()
{
	if (!m_refreshCount) {
		m_refreshCount = 3;
		for (int i = 0; i < _countof(m_commentOffset); ++i) {
			m_commentOffset[i].m_newComments = 0;
		}
		wpGetCommentCount* getCount = new wpGetCommentCount(m_blog, this);
		connect(getCount, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));
		getCount->execute(0);
	}
}

void CommentsModel::fetchMore (CommentData::CommentStatus status )
{
	wpGetComments *getComments = new wpGetComments(m_blog, wpGetComments::FetchMore, this);
	getComments->execute(0, Comment::statusToString(status), m_commentOffset[status].m_offset, s_opt.m_pageSize);
	connect(getComments, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));
}

bool CommentsModel::canFetchMore(CommentData::CommentStatus status) const
{
	return m_commentOffset[status].m_bCanFetch;
}

bool CommentsModel::removeRows(int row, int count, const QModelIndex & parent)
{
	if (count <= 0 || row < 0 || (row + count) > rowCount(parent))
		return false;
	beginRemoveRows(QModelIndex(), row, row + count - 1);

	for (int r = 0; r < count; ++r) {
		delete m_comments.takeAt(row);
	}

	endRemoveRows();
	return true;
}

void CommentsModel::deleteComment(const QModelIndex& index)
{
	if (index.isValid()) {
		int row = index.row();
		CommentItem* item = m_comments.at(row);

		beginRemoveRows(QModelIndex(), row, row);
		m_comments.removeAt(row);
		endRemoveRows();
		wpDeleteComment* cmd = new wpDeleteComment(m_blog, this);
		cmd->execute(item->comment.id());
		connect(cmd, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));
		m_commentOffset[item->comment.status()].m_count -= 1;
		//m_blog->m_commentCount[item->comment.status()] -= 1;
		emit commentsCountChanged();
		delete item;
	}
}

void CommentsModel::replyComment(const QModelIndex& index, const QString& content)
{
	if (!index.isValid() || index.row() < 0 || index.row() >= m_comments.count())
		return;
	CommentItem* item = m_comments.at(index.row());
	wpNewComment* cmd = new wpNewComment(m_blog, this);
	connect(cmd, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));	
	cmd->execute(item->comment.postId(), item->comment.id(), content);

}

void CommentsModel::editComment(const QModelIndex& index, const QString& status, const QString& content)
{
	CommentItem* item = m_comments.at(index.row());
	CommentData::CommentStatus s = Comment::stringToStatus(status);
	if (item && item->comment.status() != s) {
		--m_commentOffset[item->comment.status()].m_count;
		++m_commentOffset[s].m_count;
		/*--m_blog->m_commentCount[item->comment.status()];
		++m_blog->m_commentCount[s];*/
		emit commentsCountChanged();
		item->comment.setStatus(s);
		wpEditComment* cmd = new wpEditComment(m_blog, this);
		cmd->execute(item->comment.id(), status, content);
		connect(cmd, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));
		emit dataChanged(index, index);
	}
}

void CommentsModel::setComments(const QList<Comment>& comments)
{
	qDeleteAll(m_comments);
	m_comments.clear();
	foreach(Comment c, comments) {
		CommentItem* item = new CommentItem(c);
		m_comments << item;
	}
	reset();
}

void CommentsModel::xmlrpcFinished(bool success)
{
	wpCommand* cmd = (wpCommand*)sender();
	Q_ASSERT(cmd);
	if (!success) {
		QMessageBox::warning(NULL, tr("Error"), cmd->error());
	}

	if (cmd->type() == wpCommand::GetCommentsCount) {
		if (success) {
			wpGetCommentCount* command = (wpGetCommentCount*)cmd;
			m_commentOffset[CommentData::approve].m_count = command->approved;
			m_commentOffset[CommentData::hold].m_count = command->awaiting_moderation;
			m_commentOffset[CommentData::spam].m_count = command->spam;
			/*m_blog->m_commentCount[CommentData::approve] = command->approved;
			m_blog->m_commentCount[CommentData::hold] = command->awaiting_moderation;
			m_blog->m_commentCount[CommentData::spam] = command->spam;*/
			emit commentsCountChanged();
			for (int i = 0; i <= CommentData::spam; ++i) {
				wpGetComments *getComments = new wpGetComments(m_blog, wpGetComments::Refresh, this);
				getComments->execute(0, Comment::statusToString((CommentData::CommentStatus)i), 0, s_opt.m_pageSize);
				connect(getComments, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));
			}
		}
		else
		{
			m_refreshCount = 0;
		}
	}
	else if (cmd->type() == wpCommand::NewComment) {
		wpNewComment *command = static_cast<wpNewComment*>(sender());

		wpGetComment *getComment = new wpGetComment(m_blog, this);
		connect(getComment, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));
		getComment->execute(command->m_commentid);

	}
	else if (cmd->type() == wpCommand::GetComment) {
		wpGetComment *command = static_cast<wpGetComment*>(sender());
		bool existed = false;
		foreach(CommentItem* old, m_comments) {
			if (old->comment.id() == command->m_comment.id()) {
				existed = true;
				break;
			}
		}
		if (!existed) {
			beginInsertRows(QModelIndex(), 0, 0);
			CommentItem* item = new CommentItem(command->m_comment);
			m_comments.insert(0, item);
			endInsertRows();
		}
	}
	else if (cmd->type() == wpCommand::GetComments) {
		wpGetComments* command = static_cast<wpGetComments*>(sender());
		CommentData::CommentStatus status = Comment::stringToStatus(command->m_status);
		QList<Comment>& comments = command->m_comments;

		if (command->m_nOffset + command->m_comments.count() > m_commentOffset[status].m_offset) {
			m_commentOffset[status].m_offset = command->m_nOffset + command->m_comments.count();
		}
		m_commentOffset[status].m_bCanFetch = command->m_comments.count() >= s_opt.m_pageSize;
		// remvoe existed comments
		QList<Comment>::iterator it = comments.begin();
		while (it != comments.end())
		{
			bool bExisted =false;
			//Comment* c = *it;
			foreach(CommentItem* old, m_comments) {
				if (old->comment.id() == (*it).id()) {
					it = comments.erase(it);
					//delete c;
					bExisted = true;
					break;
				}
			}
			if (!bExisted) {
				++it;
			}
		}
		bool bNeedFetchMore = false;
		if (!comments.isEmpty()) {
			if(command->m_operationType == wpGetComments::Refresh && 
				m_refreshCount && 
				(comments.last().dateCreated() >= m_lastDateTime
				/*|| comments.last()->comment_id > m_blog->m_nLastId*/)) {
				bNeedFetchMore = true;
			}
			beginInsertRows(QModelIndex(), 0, comments.count() - 1);
			QList<Comment>::const_iterator it = comments.constEnd();
			while (it != comments.constBegin()) {
				--it;
				CommentItem* item = new CommentItem(*it);
				if (item->comment.dateCreated() > m_lastDateTime) {
					m_lastDateTime = item->comment.dateCreated();
				}
				if (item->comment.dateCreated() > m_lastDateTime) {
					++m_commentOffset[status].m_newComments;
					item->comment.setUnread(true);
				}
				m_comments.insert(0, item);
			}
			endInsertRows();
			sort(0);
		}
		if (bNeedFetchMore) {
			qDebug() << "***fetch more:" << command->m_nOffset;
			wpGetComments *getComments = new wpGetComments(m_blog, wpGetComments::Refresh, this);
			getComments->execute(0, command->m_status, command->m_nOffset + command->m_comments.count(), s_opt.m_pageSize);
			connect(getComments, SIGNAL(finished(bool)), SLOT(xmlrpcFinished(bool)));
		}
		else if (command->m_operationType == wpGetComments::Refresh && m_refreshCount) {
			if (--m_refreshCount == 0) {
				if (m_commentOffset[CommentData::approve].m_newComments || 
						m_commentOffset[CommentData::approve].m_newComments || 
						m_commentOffset[CommentData::approve].m_newComments){
							emit hasNewComments(m_commentOffset[CommentData::approve].m_newComments, m_commentOffset[CommentData::hold].m_newComments, m_commentOffset[CommentData::spam].m_newComments);
				}
				//	s_opt.save();
				emit refreshFinished();
			}
		}

		// write to file
		if (command->m_nOffset == 0 && (comments.count() > 0 || command->m_comments.count() == 0)) {
			QFile file(homeDir() + "/" + command->m_status + ".xml");
			if (file.open(QIODevice::WriteOnly)) {
				//file.write(command->rawResponse());
			}
		}
	}
}

void CommentsModel::sort( int column, Qt::SortOrder /*order*/  )
{
	if (column != 0)
		return;

	emit layoutAboutToBeChanged();

	QVector < QPair<CommentItem*,int> > sorting(m_comments.count());
	for (int i = 0; i < m_comments.count(); ++i) {
		CommentItem* item = m_comments.at(i);
		sorting[i].first = item;
		sorting[i].second = i;
	}

	//LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan);
	qSort(sorting.begin(), sorting.end(), itemGreaterThan);
	QModelIndexList fromIndexes;
	QModelIndexList toIndexes;
	for (int r = 0; r < sorting.count(); ++r) {
		CommentItem *item = sorting.at(r).first;
		toIndexes.append(createIndex(r, 0, item));
		fromIndexes.append(createIndex(sorting.at(r).second, 0, sorting.at(r).first));
		m_comments[r] = sorting.at(r).first;
	}
	changePersistentIndexList(fromIndexes, toIndexes);

	emit layoutChanged();
}

Qt::ItemFlags CommentsModel::flags ( const QModelIndex & /*index*/ ) const
{
	return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled/* | Qt::ItemIsUserCheckable*/;
}


QModelIndex CommentsModel::index ( int row, int column, const QModelIndex & /*parent*/) const 
{
	if (row < 0 || row >= m_comments.count() || column != 0)
		return QModelIndex();

	return createIndex(row, column, m_comments.value(row));
}

int CommentsModel::rowCount(const QModelIndex &parent) const
{
	return parent.isValid() ? 0 : m_comments.count();
}

QVariant CommentsModel::data(const QModelIndex &index, int role) const
{
	if (!index.isValid() || index.row() >= m_comments.count())
		return QVariant();

	if (role == Qt::DisplayRole || role == Qt::EditRole)
		return m_comments.at(index.row())->comment.content();
	else if (role == datetimeRole) {
		return m_comments.at(index.row())->comment.dateCreated();
	}
	else if (role == commentStatusRole) {
		return (int)m_comments.at(index.row())->comment.status();
	}
	else if (role == commentPointerRole) {
		return qVariantFromValue<void*>((void*)m_comments.at(index.row()));
	}
	return QVariant();
}

bool CommentsModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
	if (index.isValid() && index.row() < m_comments.count() && index.column() == 0) {
		if (role == Qt::EditRole) {
			CommentItem* item = (CommentItem*)index.internalPointer();
			QString newContent = value.toString();
			if (newContent != item->comment.content()) {
				item->comment.setContent(newContent);
				item->doc.setHtml(newContent);
				emit dataChanged(index, index);
				wpEditComment* cmd = new wpEditComment(m_blog, this);
				cmd->execute(item->comment.id(), Comment::statusToString(item->comment.status()), item->comment.content());
			}
		}
		else if (role == unreadRole) {
			CommentItem* item = (CommentItem*)index.internalPointer();
			item->comment.setUnread(value.toBool());
			emit dataChanged(index, index);
		}
	}
	return QAbstractListModel::setData(index, value, role);
}


bool CommentsModel::itemLessThan(const QPair<CommentsModel::CommentItem*,int> &left,
								 const QPair<CommentsModel::CommentItem*,int> &right)
{
	return  left.first->comment.dateCreated() < right.first->comment.dateCreated();
}

bool CommentsModel::itemGreaterThan(const QPair<CommentItem*,int> &left,
									const QPair<CommentItem*,int> &right)
{
	return (right.first->comment.dateCreated()) < (left.first->comment.dateCreated());
}

///////////////////////////////////////////////////////////
// CommentFilterModel
///////////////////////////////////////////////////////////

CommentFilterModel::CommentFilterModel(QObject* parent) : QSortFilterProxyModel(parent)
{
	setDynamicSortFilter(true);
	setFilterRole(CommentsModel::commentStatusRole);
}

CommentFilterModel::~CommentFilterModel()
{
}

QModelIndex CommentFilterModel::mapFromSource( const QModelIndex & sourceIndex ) const
{
	return QSortFilterProxyModel::mapFromSource(sourceIndex);
}

QModelIndex CommentFilterModel::mapToSource ( const QModelIndex & proxyIndex ) const
{
	return QSortFilterProxyModel::mapToSource(proxyIndex);
}

bool CommentFilterModel::canFetchMore ( const QModelIndex & /*parent*/ ) const
{
	CommentsModel* model = static_cast<CommentsModel*>(sourceModel());
	return model->canFetchMore(m_status);
}

void CommentFilterModel::fetchMore ( const QModelIndex & /*parent*/ )
{
	qDebug() << "*************** fetching more ***************\n";
	CommentsModel* model = static_cast<CommentsModel*>(sourceModel());
	return model->fetchMore(m_status);
}


void CommentFilterModel::setFilter(CommentData::CommentStatus status)
{
	QString str;
	if (status == CommentData::approve) {

		str = "approve";
	}
	else if (status == CommentData::hold) {

		str = "hold";
	}
	else if (status == CommentData::spam) {

		str = "spam";
	}
	emit filterChanged(status);
	m_status = status;
	invalidateFilter();
}

CommentsModel::CommentItem* CommentFilterModel::comment(const QModelIndex& index)
{
	CommentsModel::CommentItem* item = NULL;
	if (index.isValid()) {
		CommentFilterModel* model = (CommentFilterModel*)index.model();
		item = (CommentsModel::CommentItem*)qVariantValue<void*>(model->mapToSource(index).data(CommentsModel::commentPointerRole));
	}
	return item;
}

bool CommentFilterModel::filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const
{
	QModelIndex source_index = sourceModel()->index(source_row, 0, source_parent);
	if (!source_index.isValid()) // the column may not exist
		return true;
	CommentsModel::CommentItem* item = (CommentsModel::CommentItem*)source_index.internalPointer();
	return item->comment.status() == m_status;
}

bool CommentFilterModel::lessThan ( const QModelIndex & left, const QModelIndex & right ) const
{
	return left.data(CommentsModel::datetimeRole).toDateTime() > right.data(CommentsModel::datetimeRole).toDateTime();
}
